
#include <assert.h>
#include <memory.h>

#include "land_graphics.h"

#define LAND_OGL_CORE

#include "init.h"

static struct {
	LandVertex* curBind;
}ctx = { 0 };

static void
enableVertexAttr(LandVertex* vertex) {
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	char* offset = NULL;
	GLsizei size = sizeof(GLfloat) * 16;
	for (unsigned i = 0; i < 4; i++) {
		offset = (char*)(sizeof(GLfloat) * 4 * i);
		glVertexAttribPointer(i, 4,
			GL_FLOAT, GL_FALSE, size, (void*)offset);
		glEnableVertexAttribArray(i);
	}
}

static unsigned
getTypeSize(int type) {
	switch (type) {
	case LAND_GRAPHICS_INT:
		return sizeof(GLint);
	case LAND_GRAPHICS_TUNSIGNED:
		return sizeof(GLuint);
	case LAND_GRAPHICS_TFLOAT:
		return sizeof(GLfloat);
	case LAND_GRAPHICS_TVECTOR:
		return sizeof(GLfloat) * 4;
	case LAND_GRAPHICS_TMATRIX:
		return sizeof(GLfloat) * 16;
	default:
		return 0;
	}
}

static unsigned
copyVertex(GLfloat* data, LandPoint* point, unsigned count) {
	unsigned offset = 0;
	for (unsigned i = 0; i < count; i++) {
		for (unsigned j = 0; j < 16; j++) {
			data[offset + j] = point[i].B[j];
		}
		offset += 16;
	}
	return offset;
}

static void
uploadPoints(LandVertex* vertex) {
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	unsigned total = landGetListTotal(vertex->points);
	if (!total) {
		return;
	}
	GLsizei size = sizeof(GLfloat) * 16;
	GLsizei totalSize = size * total;
	LandPoint* points;
	unsigned count;
	GLfloat* data = malloc(totalSize);
	unsigned offset = 0;
	landBeginList(vertex->points);
	while (landNextList(vertex->points, &points, &count)) {
		offset += copyVertex(data + offset, points, count);
	}
	glBufferData(GL_ARRAY_BUFFER, totalSize, data, GL_STATIC_DRAW);
	free(data);
}

static void
uploadIndexs(LandVertex* vertex) {
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
	unsigned total = landGetListTotal(vertex->indexs);
	if (!total) {
		return;
	}
	unsigned totalSize = sizeof(unsigned) * total;
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalSize, NULL, GL_STATIC_DRAW);
	GLintptr offset = 0;
	GLsizeiptr subSize = 0;
	unsigned count;
	unsigned* indexs;
	landBeginList(vertex->indexs);
	while (landNextList(vertex->indexs, &indexs, &count)) {
		subSize = count * sizeof(unsigned);
		unsigned temp[1000];
		memcpy(temp, indexs, totalSize);
		glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, subSize, indexs);
		offset += subSize;
	}
}


static size_t
sizeofVertex() {
    return sizeof(LandOglVertex);
}

static int
newVertex(LandVertex* vertex, unsigned cap) {
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	oglVertex->created = 0;
	return 0;
}


static void
freeVertex(LandVertex* vertex) {
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	if (ctx.curBind == vertex) {
		ctx.curBind = NULL;
	}
	if (oglVertex->created) {
		glDeleteBuffers(1, &oglVertex->glVbo);
		glDeleteBuffers(1, &oglVertex->glIbo);
		glDeleteVertexArrays(1, &oglVertex->glVao);
	}
}


static void
uploadVertex(LandVertex* vertex) {
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	if (!oglVertex->created) {
		glGenVertexArrays(1, &oglVertex->glVao);
		glBindVertexArray(oglVertex->glVao);
		glGenBuffers(1, &oglVertex->glVbo);
		glGenBuffers(1, &oglVertex->glIbo);
		glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
		enableVertexAttr(vertex);
		ctx.curBind = vertex;
		oglVertex->created = 1;
	}
	ctx.curBind = vertex;
	glBindVertexArray(oglVertex->glVao);
	uploadPoints(vertex);
	uploadIndexs(vertex);
}

static LandVertexBackend backend = {
	sizeofVertex,
	newVertex,
	freeVertex,
	uploadVertex,
};

LandVertexBackend*
landGetOglVertexBackend() {
	return &backend;
}

void
landBindOglVertex(LandVertex* vertex) {
	if (ctx.curBind == vertex) {
		return;
	}
	ctx.curBind = vertex;
	LandOglVertex* oglVertex = (LandOglVertex*)vertex;
	glBindVertexArray(oglVertex->glVao);
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
}
